/***************************************************************************
 *   Copyright (C) 2006 by Yuri Ovcharenko                                 *
 *   amwsoft@gmail.com                                                     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "dc.h"
#include "version.h"
#include "dcplugin.h"
#include "serial.h"
#include "console.h"
#include "serialsettings.h"
#include "consolesettings.h"
#include "pluginmanager.h"
#include "aboutdialog.h"

#include <QtGui>

HexValidator::HexValidator(QObject *parent)
  : QValidator(parent)
{
}

HexValidator::~HexValidator()
{
}

QValidator::State HexValidator::validate (QString & input, int & pos) const
{
  if (input.isEmpty())
    return QValidator::Intermediate;

  input = input.toUpper();

  if ((pos < 1) || (pos > input.size()))
       return QValidator::Intermediate;

  if ((input[pos - 1] == ' ') ||
       ((input[pos - 1] >= '0') && (input[pos - 1] <= '9')) ||
       ((input[pos - 1] >= 'A') && (input[pos - 1] <= 'F')))
    return QValidator::Acceptable;

  return QValidator::Invalid;
}

DC::DC( QWidget *parent )
    : QMainWindow( parent )
{
  QVBoxLayout *lv;
  QHBoxLayout *lh;
  QMenu *menu;
  QAction *act;

  appPrefix.setPath(QFileInfo(QString("%1").arg(qApp->applicationDirPath())).filePath());
  appPrefix.cdUp();

  setWindowTitle(tr("Developer console"));
  setWindowIcon(QIcon(":/resources/icons/dc.png"));

  stBar = statusBar();
  msgConsole = new QLabel("");
  stBar->addPermanentWidget(msgConsole);
  msgSerial = new QLabel("");
  stBar->addPermanentWidget(msgSerial);
  actClosePlugin = 0;

  serialSettings = 0;
  consoleSettings = 0;

  settings = new QSettings(QString("%1/%2").arg(QDir::homePath()).arg(".dc/dc.conf"), QSettings::NativeFormat, this);
  resize(settings->value("MainWindow/Size", QSize(300, 200) ).toSize());
  move(settings->value("MainWindow/Pos", QPoint(10, 10)).toPoint());
  QByteArray ba(settings->value("MainWindow/State", QByteArray()).toByteArray());
  if (! ba.isEmpty())
    restoreState(ba);

  serial = new Serial(this);
  connect(serial, SIGNAL(receive(const QByteArray &)), this, SLOT(slotReceive(const QByteArray &)));
  connect(serial, SIGNAL(portOpened()), this, SLOT(slotPortOpened()));
  port = settings->value("Serial/Port", QString("/dev/tts/0")).toString();
  baud = settings->value("Serial/Baud", 115200).toInt();
  serial->setPort(port);
  serial->setBaud(baud);
  if (! serial->openPort())
  {
    msgSerial->setText(tr("Error"));
    QMessageBox::critical(this, tr("Error"), QString(tr("Can not open serial port.\nDevice: %1\nError: %2")).arg(port).arg(serial->errorStr()));
  }
  else
    msgSerial->setText(QString("%1 | %2").arg(port).arg(baud));

  tabs = new QTabWidget(this);
  connect(tabs, SIGNAL(currentChanged(int)), this, SLOT(slotTabChanged(int)));
  setCentralWidget(tabs);

  consoleFrame = new QFrame();
  tabs->addTab(consoleFrame, tr("Console"));

  lv = new QVBoxLayout(consoleFrame);

  QScrollArea *scroll = new QScrollArea();
  scroll->setFocusPolicy(Qt::NoFocus);
  lv->addWidget(scroll);
  hexConsole = settings->value("Console/Hex", false).toBool();
  console = new Console();
  console->setConsoleSize(settings->value("Console/Size", QSize(80, 25)).toSize());
  QFont fnt(settings->value("Console/Font", "Fixed").toString(), 10);
  console->setConsoleFont(fnt);
  connect(console, SIGNAL(keyPress(const QChar &)), this, SLOT(slotConsoleChar(const QChar &)));
  scroll->setWidget(console);
  msgConsole->setText(QString("%1x%2 | %3").arg(console->consoleSize().width()).arg(console->consoleSize().height()).
      arg(hexConsole ? "HEX" : "CHAR"));

  lh = new QHBoxLayout();
  lv->addLayout(lh);

  lh->addWidget(new QLabel("HEX: "));
  cbTransmit = new QComboBox();
  cbTransmit->setEditable(true);
  cbTransmit->setDuplicatesEnabled(false);
  cbTransmit->setValidator(new HexValidator(this));
  connect(cbTransmit, SIGNAL(activated(const QString &)), this, SLOT(slotTransmit(const QString &)));
  lh->addWidget(cbTransmit, 1);

  pluginManager = new PluginManager(this);
  pluginManager->setSettingsDir(QString("%1/%2").arg(QDir::homePath()).arg(".dc/plugins"));
  QStringList list = settings->value("Plugins/Directories", QString()).toString().split(":", QString::SkipEmptyParts);
  QStringList activeList = settings->value("Plugins/Active", QString()).toString().split(":", QString::SkipEmptyParts);
  list.prepend(QString("%1/lib/dc/plugins").arg(appPrefix.path()));
  pluginManager->searchPlugins(list);

  for (int i = 0; i < activeList.size(); i++)
    slotActivatePlugin(activeList[i]);

/***************************************************************************
 *  File Menu
 */

  menu = menuBar()->addMenu(tr("File"));

  act = new QAction(tr("Quit", "File|Quit"), this);
  act->setShortcut(tr("Ctrl+Q"));
  connect(act, SIGNAL(triggered()), this, SLOT(close()));
  menu->addAction(act);

/***************************************************************************
 *  Plugins Menu
 */

  menu = menuBar()->addMenu(tr("Plugins"));

  pluginsMenu = menu->addMenu(tr("Plugins"));
  for (int i = 0; i < pluginManager->count(); i++)
  {
    act = new QAction(pluginManager->pluginName(i), this);
    act->setData(pluginManager->pluginName(i));
    connect(act, SIGNAL(triggered()), this, SLOT(slotActivatePlugin()));
    pluginsMenu->addAction(act);
  }

  menu->addSeparator();

  actClosePlugin = act = new QAction(tr("Close"), this);
  connect(act, SIGNAL(triggered()), this, SLOT(slotDeactivatePlugin()));
  menu->addAction(act);

  act = new QAction(tr("Configure..."), this);
  connect(act, SIGNAL(triggered()), this, SLOT(slotConfigurePlugins()));
  menu->addAction(act);

/***************************************************************************
 *  Settings Menu
 */
  menu = menuBar()->addMenu(tr("Settings"));

  act = new QAction(tr("Serial Port...", "Settings|Serial Port"), this);
  connect(act, SIGNAL(triggered()), this, SLOT(slotSerialSettings()));
  menu->addAction(act);

  act = new QAction(tr("Console...", "Settings|Console"), this);
  connect(act, SIGNAL(triggered()), this, SLOT(slotConsoleSettings()));
  menu->addAction(act);

/***************************************************************************
 *  Help Menu
 */
  menu = menuBar()->addMenu(tr("Help"));

  act = new QAction(tr("About", "Help|About"), this);
  connect(act, SIGNAL(triggered()), this, SLOT(slotAbout()));
  menu->addAction(act);

  act = new QAction(tr("About QT", "Help|About QT"), this);
  connect(act, SIGNAL(triggered()), this, SLOT(slotAboutQt()));
  menu->addAction(act);

  QString curPlugin = settings->value("Plugins/Current", QString()).toString();
  if (curPlugin.isEmpty())
    tabs->setCurrentIndex(0);
  else
  {
    for (int i = 1; i < tabs->count(); i++)
    {
      if (tabs->tabText(i) == curPlugin)
      {
        tabs->setCurrentIndex(i);
        break;
      }
    }
  }
}

DC::~DC()
{
}

/***************************************************************************
 *  Events
 */

void DC::closeEvent( QCloseEvent * e)
{
  saveSettings();
  for (int i = 0; i < pluginManager->count(); i++)
    pluginManager->savePluginSettings(i);
  e->accept();
}

/***************************************************************************
 *  Functions
 */

void DC::saveSettings()
{
  settings->setValue("MainWindow/Size", size());
  settings->setValue("MainWindow/Pos", pos());
  settings->setValue("MainWindow/State", saveState());

  settings->setValue("Plugins/Directories", pluginManager->dirs().join(":"));
  QStringList activeList;
  for (int i = 1; i < tabs->count(); i++)
    activeList.append(tabs->tabText(i));
  settings->setValue("Plugins/Active", activeList.join(":"));
  QString curPlugin;
  if (tabs->currentIndex() > 0)
    curPlugin = tabs->tabText(tabs->currentIndex());
  else
    curPlugin = QString();
  settings->setValue("Plugins/Current", curPlugin);

  settings->sync();
}

/***************************************************************************
 *  Help Menu slots
 */

void DC::slotAbout()
{
	QString s = QString();
	QFile f(":/COPYING");
	if (f.open(QIODevice::ReadOnly)) {
		s = f.readAll();
		f.close();
	}

	AboutDialog *d = new AboutDialog(this, QIcon(":/resources/icons/dc-big.png"),
					 QString(tr("About %1")).arg(qApp->applicationName()),
					 QString(tr(
							 "<b>%1 version %2</b><br>"
							 "This application "
							 "for communicate with various devices attached to serial port.<br><br>"
							 "The primary job is display received data to the user in different represrntation:<br>"
							 "as characters or as stream of raw data in HEX.<br>"
							 "Also it is allowed input from user as a characters or as raw data in HEX.<br>"
							 "<br>"
							 "This program is free software; you can redistribute it and/or modify<br>"
							 "it under the terms of the GNU General Public License as published by<br>"
							 "the Free Software Foundation; either version 2 of the License, or<br>"
							 "(at your option) any later version.<br>"
							 "<br>"
							 "Copyright (C) 2007 by Yuri Ovcharenko <a href=\"mailto:%4\">%4</a><br>"
							 "Visit <a href=\"%3\">%3</a> to obtain new version."
						   )
						)
							 .arg(qApp->applicationName()).arg(QString(DCVERSION))
							 .arg(QString("http://%1/p/dc/").arg(qApp->organizationDomain()))
							 .arg("amwsoft@gmail.com"),
					 s
					);
	d->exec();
	delete d;
}

void DC::slotAboutQt()
{
  QMessageBox::aboutQt(this, QString(tr("About QT")));
}
/***************************************************************************
 *  Settings Menu slots
 */

void DC::slotSerialSettings()
{
  if (! serialSettings)
    serialSettings = new SerialSettings(this);
  serialSettings->setSettings(settings->value("Serial/Port", QString("/dev/tts/0")).toString(),
                              settings->value("Serial/Baud", 115200).toInt()
                             );
  if (serialSettings->exec() == QDialog::Accepted)
  {
    QString port;
    int baud;
    serialSettings->settings(port, baud);
    settings->setValue("Serial/Port", port);
    settings->setValue("Serial/Baud", baud);
    serial->setPort(port);
    if (!serial->setBaud(baud))
    {
      QMessageBox::critical(this, tr("Error"), QString(tr("%1")).arg(serial->errorStr()));
      msgSerial->setText(tr("Error"));
      return;
    }
    msgSerial->setText(QString("%1 | %2").arg(port).arg(baud));
  }
}

void DC::slotConsoleSettings()
{
  if (! consoleSettings)
    consoleSettings = new ConsoleSettings(this);
  consoleSettings->setSettings(settings->value("Console/Size", QSize(80, 25)).toSize(),
                               settings->value("Console/Hex", false).toBool(),
                               settings->value("Console/Font", "Fixed").toString());
  if (consoleSettings->exec() == QDialog::Accepted)
  {
    QSize sz;
    bool hex;
    QString fnt;
    consoleSettings->settings(sz, hex, fnt);
    settings->setValue("Console/Size", sz);
    settings->setValue("Console/Hex", hex);
    settings->setValue("Console/Font", fnt);
    console->setConsoleFont(QFont(fnt, 10));
    console->setConsoleSize(sz);
    hexConsole = hex;
    msgConsole->setText(QString("%1x%2 | %3").arg(sz.width()).arg(sz.height()).arg(hexConsole ? "HEX" : "CHAR"));
  }
}
/***************************************************************************
 *  Console slots
 */

void DC::slotConsoleChar(const QChar & c)
{
  QByteArray ba;
  ba.append(c);
  if (!serial->send(ba))
    stBar->showMessage(serial->errorStr(), 5000);
}

void DC::slotTransmit(const QString & d)
{
  QStringList list(d.split(" ", QString::SkipEmptyParts));
  QByteArray ba;
  for (int i = 0; i < list.size(); i++)
  {
    bool ok;
    unsigned char c = list[i].toUInt(&ok, 16);
    if (!ok)
      return;
    ba.append(c);
  }
  serial->send(ba);
}

/***************************************************************************
 *  Serial slots
 */

void DC::slotReceive(const QByteArray & d)
{
  if (!console)
    return;
  if (!hexConsole)
    console->append(d);
  else
  {
    QString s;
    for (int i = 0; i < d.size(); i++)
    {
      s += QString(" 0x%1").arg(QString("%1").arg((unsigned char)d[i], 2, 16, QChar('0')).toUpper());
    }
    console->append(s);
  }
}

void DC:: slotPortOpened()
{
  msgSerial->setText(QString("%1 | %2").arg(port).arg(baud));
}

/***************************************************************************
 *  Plugins slots
 */

void DC::slotActivatePlugin()
{
  QAction *a = qobject_cast<QAction *>(sender());
  if (!a)
    return;

  QString s = a->data().toString();
  if (s.isEmpty())
    return;

  slotActivatePlugin(s);
}

void DC::slotActivatePlugin(const QString & pname)
{
  for (int i = 1; i < tabs->count(); i++)
  {
    if (tabs->tabText(i) == pname)
    {
      tabs->setCurrentIndex(i);
      return;
    }
  }

  for (int i = 0; i < pluginManager->count(); i++)
  {
    if (pluginManager->pluginName(i) == pname)
    {
      QWidget *w = pluginManager->pluginWidget(i);
      if (w)
      {
        pluginManager->restorePluginSettings(i);
        tabs->addTab(w, pluginManager->pluginName(i));
        tabs->setCurrentIndex(tabs->count() - 1);
	switch (pluginManager->plugin(i)->portConnection()) {
		case DCPlugin::PortConcurent:
			connect(w, SIGNAL(atStart(bool)), this, SLOT(slotAtPluginStart(bool)));
			connect(w, SIGNAL(stopped(bool)), this, SLOT(slotPluginStopped(bool)));
			break;
		case DCPlugin::PortDirect:
			qDebug() << "WARNING: PortDirect connection type is not supported at this time!";
// 			connect(w, SIGNAL(), this, SLOT());
// 			connect(w, SIGNAL(), this, SLOT());
			break;
	}
      }
      break;
    }
  }
}

void DC::slotDeactivatePlugin()
{
  if (tabs->currentIndex() <= 0)
    return;

  int index = tabs->currentIndex();
  QString pname = tabs->tabText(index);
  for (int i = 0; i < pluginManager->count(); i++)
  {
    if (pluginManager->pluginName(i) == pname)
    {
      tabs->removeTab(index);
      pluginManager->savePluginSettings(i);
      pluginManager->deactivatePlugin(i);
      break;
    }
  }
}

void DC::slotConfigurePlugins()
{
  QMessageBox::information(this, "Info", "Sorry, Plugins configuration not implemented at this time.");
}

void DC::slotTabChanged(int index)
{
  if (actClosePlugin)
    actClosePlugin->setEnabled(index > 0);
  /*
  for (int i = 0; i < pluginManager->count(); i++)
  {
    if (tabs->tabText(index) == pluginManager->pluginName(i))
    {
      connectPlugin(i);
      break;
    }
  }
  */
}

void DC::slotAtPluginStart(bool needClose)
{
	if (needClose)
		serial->closePort();
}

void DC::slotPluginStopped(bool canOpen)
{
	if (! serial->openPort())
	{
		msgSerial->setText(tr("Error"));
		QMessageBox::critical(this, tr("Error"), QString(tr("Can not open serial port.\nDevice: %1\nError: %2")).arg(port).arg(serial->errorStr()));
	}
	else
		msgSerial->setText(QString("%1 | %2").arg(port).arg(baud));
}

/* End of file */
